#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
K线模式识别模块

提供各种K线形态的识别算法：
- 上影线形态（见顶/突破反包）
- 十字星（犹豫/转折）
- 锤子线（底部反转）

设计原则：
1. 每个形态检测器独立工作
2. 返回标准化的结果格式
3. 支持差异化市值配置
"""

import pandas as pd
import numpy as np
from typing import Dict, Tuple, Optional
from datetime import datetime
import logging

logger = logging.getLogger(__name__)


class CandlestickPattern:
    """K线形态基础类 - 提供通用计算方法"""
    
    @staticmethod
    def calculate_shadow_ratios(candle: pd.Series) -> Dict[str, float]:
        """
        计算上下影线比例
        
        Args:
            candle: 单根K线数据，包含 open, high, low, close
        
        Returns:
            {
                'upper_shadow': 上影线长度,
                'lower_shadow': 下影线长度,
                'body': 实体长度,
                'total_range': K线总长度,
                'upper_ratio': 上影线比例,
                'lower_ratio': 下影线比例,
                'body_ratio': 实体比例
            }
        """
        open_price = candle['open']
        high = candle['high']
        low = candle['low']
        close = candle['close']
        
        # 实体部分
        body_top = max(open_price, close)
        body_bottom = min(open_price, close)
        
        # 影线长度
        upper_shadow = high - body_top
        lower_shadow = body_bottom - low
        body = abs(close - open_price)
        total_range = high - low
        
        # 避免除零
        if total_range == 0:
            return {
                'upper_shadow': 0,
                'lower_shadow': 0,
                'body': 0,
                'total_range': 0,
                'upper_ratio': 0,
                'lower_ratio': 0,
                'body_ratio': 0
            }
        
        # 计算比例
        upper_ratio = upper_shadow / total_range
        lower_ratio = lower_shadow / total_range
        body_ratio = body / total_range
        
        return {
            'upper_shadow': upper_shadow,
            'lower_shadow': lower_shadow,
            'body': body,
            'total_range': total_range,
            'upper_ratio': upper_ratio,
            'lower_ratio': lower_ratio,
            'body_ratio': body_ratio
        }
    
    @staticmethod
    def is_bullish_candle(candle: pd.Series) -> bool:
        """判断是否为阳线"""
        return candle['close'] > candle['open']
    
    @staticmethod
    def is_bearish_candle(candle: pd.Series) -> bool:
        """判断是否为阴线"""
        return candle['close'] < candle['open']
    
    @staticmethod
    def calculate_price_position(df: pd.DataFrame, lookback: int = 60) -> float:
        """
        计算价格位置（相对历史高低点的位置）
        
        Args:
            df: K线数据DataFrame
            lookback: 回溯天数
        
        Returns:
            价格位置比例（0-1），1表示最高点，0表示最低点
        """
        if len(df) == 0:
            return 0.5
        
        # 取最近lookback天的数据
        recent_data = df.tail(lookback)
        
        highest = recent_data['high'].max()
        lowest = recent_data['low'].min()
        current = df.iloc[-1]['close']
        
        if highest == lowest:
            return 0.5
        
        position = (current - lowest) / (highest - lowest)
        return max(0.0, min(1.0, position))
    
    @staticmethod
    def calculate_volume_ratio(df: pd.DataFrame, period: int = 5) -> float:
        """
        计算量比（当前成交量 / 过去N日平均成交量）
        
        Args:
            df: K线数据DataFrame
            period: 平均周期
        
        Returns:
            量比
        """
        if len(df) < period + 1:
            return 1.0
        
        current_volume = df.iloc[-1]['volume']
        avg_volume = df.iloc[-period-1:-1]['volume'].mean()
        
        if avg_volume == 0:
            return 1.0
        
        return current_volume / avg_volume
    
    @staticmethod
    def check_strong_uptrend(df: pd.DataFrame, ma_spacing_min: float = 4.0) -> bool:
        """
        检查是否处于强势上涨趋势
        
        条件：
        1. 多头排列（MA5 > MA10 > MA20）
        2. 均线间距 ≥ ma_spacing_min%
        
        Args:
            df: K线数据DataFrame，需包含MA5, MA10, MA20
            ma_spacing_min: 最小均线间距（百分比）
        
        Returns:
            是否强势上涨
        """
        if len(df) == 0:
            return False
        
        current = df.iloc[-1]
        
        # 检查是否有均线数据
        if 'MA5' not in current or 'MA10' not in current or 'MA20' not in current:
            return False
        
        ma5 = current['MA5']
        ma10 = current['MA10']
        ma20 = current['MA20']
        
        # 检查多头排列
        if not (ma5 > ma10 > ma20):
            return False
        
        # 计算均线间距
        if ma20 == 0:
            return False
        
        ma_spacing = (ma5 - ma20) / ma20 * 100
        
        return ma_spacing >= ma_spacing_min


class UpperShadowPattern(CandlestickPattern):
    """
    上影线形态检测器
    
    功能：
    1. 卖出信号：高位长上影线（见顶信号）
    2. 买入信号：低位上影线突破（反包信号）- 可选
    """
    
    def __init__(self, config: Dict):
        """
        初始化上影线检测器
        
        Args:
            config: 上影线配置参数（来自 technical_patterns.yaml）
        """
        self.config = config
        self.name = 'upper_shadow'
    
    def analyze(self, 
                df: Optional[pd.DataFrame] = None,
                today_data: Optional[Dict] = None,
                stock_type: str = 'medium',
                position_info: Optional[Dict] = None,
                mode: str = 'daily',
                metrics: Optional[Dict] = None) -> Optional[Dict]:
        """
        分析上影线形态（支持日线和分时两种模式）
        
        Args:
            df: K线数据DataFrame（日线模式使用）
            today_data: 当日累计数据（分时模式使用），格式：
                {'open': 开盘价, 'high': 最高价, 'low': 最低价, 'last': 当前价, 'volume': 成交量}
            stock_type: 股票类型 (large/medium/small)
            position_info: 持仓信息（可选）
            mode: 'intraday'=分时模式 | 'daily'=日线模式
            metrics: 预计算的公共指标（共享策略数据），格式：
                {'volume_ratio': 量比, 'price_position': 价格位置, 'avg_volume': 平均量等}
        
        Returns:
            形态分析结果，如果无形态返回 None
        """
        # ⭐ 根据模式选择数据源
        if mode == 'intraday':
            # 分时模式：使用today_data（共享策略的计算结果）
            if not today_data:
                return None
            
            # 计算日内累计上影线（修复：使用实体顶部计算，与日线模式一致）
            intraday_high = today_data.get('high', 0)
            intraday_low = today_data.get('low', 0)
            intraday_open = today_data.get('open', 0)
            current_price = today_data.get('last', 0)
            
            # ⭐ 修复：使用实体顶部计算上影线，而不是用当前价格
            # 日线模式：upper_shadow = high - max(open, close)
            # 分时模式：upper_shadow = high - max(open, last)
            body_top = max(intraday_open, current_price)
            body_bottom = min(intraday_open, current_price)
            
            intraday_range = intraday_high - intraday_low
            upper_shadow = intraday_high - body_top
            lower_shadow = body_bottom - intraday_low
            body = abs(current_price - intraday_open)
            
            # 振幅过小，不分析
            min_range = self.config.get('min_kline_range', 0.2)  # 分时模式阈值更小
            if intraday_range < min_range:
                return None
            
            # 构建shadow_data（兼容原有逻辑）
            shadow_data = {
                'upper_shadow': upper_shadow,
                'lower_shadow': lower_shadow,
                'body': body,
                'total_range': intraday_range,
                'upper_ratio': upper_shadow / intraday_range if intraday_range > 0 else 0,
                'lower_ratio': lower_shadow / intraday_range if intraday_range > 0 else 0,
                'body_ratio': body / intraday_range if intraday_range > 0 else 0
            }
            
            # 构建虚拟df（用于兼容原有的_check_sell_pattern逻辑）
            # 注意：分时模式使用'last'字段，需要映射为'close'以兼容日线逻辑
            virtual_data = today_data.copy()
            virtual_data['close'] = virtual_data.get('last', current_price)  # 映射last为close
            virtual_df = pd.DataFrame([virtual_data])
            
        else:
            # 日线模式：使用DataFrame
            if df is None or len(df) == 0:
                return None
            
            current = df.iloc[-1]
            shadow_data = self.calculate_shadow_ratios(current)
            
            min_range = self.config.get('min_kline_range', 0.5)
            if shadow_data['total_range'] < min_range:
                return None
            
            virtual_df = df
        
        # 优先检查卖出型上影线
        sell_result = self._check_sell_pattern(virtual_df, shadow_data, stock_type, position_info, metrics)
        if sell_result:
            return sell_result
        
        # 检查买入型上影线（如果启用）
        if self.config.get('buy_signals', {}).get('enabled', False):
            buy_result = self._check_buy_pattern(virtual_df, shadow_data, stock_type, metrics)
            if buy_result:
                return buy_result
        
        return None
    
    def _check_sell_pattern(self, 
                           df: pd.DataFrame, 
                           shadow_data: Dict,
                           stock_type: str,
                           position_info: Optional[Dict],
                           metrics: Optional[Dict] = None) -> Optional[Dict]:
        """
        检查卖出型上影线（见顶信号）
        
        条件：
        1. 上影线比例达标
        2. 量能达标
        3. 价格位置达标
        4. 评分达到阈值
        """
        # 获取股票类型配置
        cap_mapping = {'large': 'large_cap', 'medium': 'medium_cap', 'small': 'small_cap'}
        config_key = cap_mapping.get(stock_type, 'medium_cap')
        sell_config = self.config['sell_signals'][config_key]
        
        # 基础条件检查
        upper_ratio = shadow_data['upper_ratio']
        upper_shadow = shadow_data.get('upper_shadow', 0)
        total_range = shadow_data.get('total_range', 0)
        
        # ⭐ 调试信息：输出关键数据用于排查
        logger.debug(f"上影线形态检查: 上影线={upper_shadow:.2f}, 总振幅={total_range:.2f}, 上影线比例={upper_ratio:.1%}")
        
        # 1. 上影线比例必须达标
        if upper_ratio < sell_config['shadow_ratio']:
            return None
        
        # 2. ⭐ 上影线必须有实际长度（绝对值检查），避免因为振幅太小而比例虚高
        # 例如：如果总振幅只有0.5元，上影线0.3元，比例60%很高，但实际长度很小
        min_upper_shadow = sell_config.get('min_upper_shadow', 0.5)  # 默认上影线至少0.5元（可配置）
        if upper_shadow < min_upper_shadow:
            logger.debug(f"上影线形态被过滤：上影线实际长度{upper_shadow:.2f}元 < 最小阈值{min_upper_shadow:.2f}元，可能是计算误差")
            return None
        
        # ⭐ 优先使用预计算的公共指标（避免重复计算）
        if metrics:
            volume_ratio = metrics.get('volume_ratio', 1.0)
            price_position = metrics.get('price_position', 0.5)
            logger.debug(f"📊 使用预计算metrics: 量比={volume_ratio:.2f} 价格位置={price_position:.2%}")
        else:
            # 兜底：自行计算（日线模式或未传入metrics时）
            volume_ratio = self.calculate_volume_ratio(df)
            price_position = self.calculate_price_position(
                df, 
                self.config.get('market_environment', {}).get('price_position', {}).get('lookback_days', 60)
            )
            logger.debug(f"📊 自行计算metrics: 量比={volume_ratio:.2f} 价格位置={price_position:.2%}")
        
        # 检查量能和位置
        if volume_ratio < sell_config['volume_min']:
            return None
        
        if price_position < sell_config['position_min']:
            return None
        
        # 计算信号强度
        strength = self._calculate_sell_strength(
            upper_ratio,
            volume_ratio,
            price_position,
            df,
            sell_config,
            position_info
        )
        
        # 检查是否达到阈值
        threshold = self.config['strength_threshold']['sell_suggest']
        if strength < threshold:
            return None
        
        # 信号生成成功（改为DEBUG级别，策略层会输出详细日志）
        logger.debug(f"✅ 技术形态信号: upper_shadow 强度:{strength:.2f}")
        
        return {
            'pattern': 'upper_shadow',
            'signal_type': 'sell',
            'strength': strength,
            'stock_type': stock_type,
            'config_key': config_key,
            'details': {
                'upper_shadow_ratio': upper_ratio,
                'volume_ratio': volume_ratio,
                'price_position': price_position,
                'shadow_length': shadow_data['upper_shadow'],
                'total_range': shadow_data['total_range'],
                'is_bearish': self.is_bearish_candle(df.iloc[-1]),
                'threshold': threshold,
            }
        }
    
    def _calculate_sell_strength(self,
                                 upper_ratio: float,
                                 volume_ratio: float,
                                 price_position: float,
                                 df: pd.DataFrame,
                                 config: Dict,
                                 position_info: Optional[Dict]) -> float:
        """
        计算卖出信号强度（0-5分制）
        
        评分构成：
        - 上影线因子：0-2分
        - 量能因子：0-1分
        - 位置因子：0-1分
        - 盈利因子：0-1分
        - 趋势因子：0-0.5分
        """
        score = 0.0
        scoring = config.get('scoring', {})
        
        # 1. 上影线因子（0-2分）
        shadow_scoring = scoring.get('shadow', {})
        if upper_ratio >= shadow_scoring.get('excellent', [0.75, 2.0])[0]:
            score += shadow_scoring['excellent'][1]
        elif upper_ratio >= shadow_scoring.get('good', [0.65, 1.5])[0]:
            score += shadow_scoring['good'][1]
        elif upper_ratio >= shadow_scoring.get('normal', [0.60, 1.0])[0]:
            score += shadow_scoring['normal'][1]
        
        # 2. 量能因子（0-1分）
        volume_scoring = scoring.get('volume', {})
        if volume_ratio >= volume_scoring.get('excellent', [2.0, 1.0])[0]:
            score += volume_scoring['excellent'][1]
        elif volume_ratio >= volume_scoring.get('good', [1.5, 0.5])[0]:
            score += volume_scoring['good'][1]
        
        # 3. 位置因子（0-1分）
        position_scoring = scoring.get('position', {})
        if price_position >= position_scoring.get('excellent', [0.85, 1.0])[0]:
            score += position_scoring['excellent'][1]
        elif price_position >= position_scoring.get('good', [0.75, 0.5])[0]:
            score += position_scoring['good'][1]
        
        # 4. 盈利因子（0-1分）- 仅在有持仓时计算
        if position_info:
            profit_ratio = position_info.get('profit_ratio', 0)
            profit_scoring = scoring.get('profit', {})
            if profit_ratio >= profit_scoring.get('excellent', [0.15, 1.0])[0]:
                score += profit_scoring['excellent'][1]
            elif profit_ratio >= profit_scoring.get('good', [0.10, 0.5])[0]:
                score += profit_scoring['good'][1]
        
        # 5. 趋势因子（0-0.5分）
        market_env = self.config.get('market_environment', {})
        strong_uptrend_config = market_env.get('strong_uptrend', {})
        if self.check_strong_uptrend(
            df, 
            strong_uptrend_config.get('ma_spacing_min', 4.0)
        ):
            score += scoring.get('trend', {}).get('strong_uptrend', 0.5)
        
        return min(score, 5.0)
    
    def _check_buy_pattern(self, 
                          df: pd.DataFrame, 
                          shadow_data: Dict,
                          stock_type: str,
                          metrics: Optional[Dict] = None) -> Optional[Dict]:
        """
        检查买入型上影线（突破反包型）
        
        注：当前版本仅记录日志，不实际触发
        """
        # 获取配置
        cap_mapping = {'large': 'large_cap', 'medium': 'medium_cap', 'small': 'small_cap'}
        config_key = cap_mapping.get(stock_type, 'medium_cap')
        buy_config = self.config['buy_signals'][config_key]
        
        # 基础检查
        upper_ratio = shadow_data['upper_ratio']
        if upper_ratio < buy_config['shadow_ratio']:
            return None
        
        # ⭐ 优先使用预计算的公共指标（避免重复计算）
        if metrics:
            volume_ratio = metrics.get('volume_ratio', 1.0)
            price_position = metrics.get('price_position', 0.5)
        else:
            # 兜底：自行计算
            volume_ratio = self.calculate_volume_ratio(df)
            price_position = self.calculate_price_position(df)
        
        # 量能检查
        volume_range = buy_config['volume_range']
        if not (volume_range[0] <= volume_ratio <= volume_range[1]):
            return None
        
        # 价格位置检查
        if price_position > buy_config['position_max']:
            return None
        
        return {
            'pattern': 'upper_shadow',
            'signal_type': 'buy',
            'strength': 0,  # 暂不计算强度
            'log_only': self.config['buy_signals'].get('log_only', True),
            'stock_type': stock_type,
            'details': {
                'upper_shadow_ratio': upper_ratio,
                'volume_ratio': volume_ratio,
                'price_position': price_position,
            }
        }


class DojiPattern(CandlestickPattern):
    """
    十字星形态检测器
    
    功能：
    识别十字星形态，通常表示市场犹豫或转折点
    
    典型特征：
    - 开盘价和收盘价几乎相同
    - 上下影线较长
    - 实体很小
    """
    
    def __init__(self, config: Dict):
        """
        初始化十字星检测器
        
        Args:
            config: 十字星配置参数
        """
        self.config = config
        self.name = 'doji'
    
    def analyze(self, 
                df: Optional[pd.DataFrame] = None,
                today_data: Optional[Dict] = None,
                stock_type: str = 'medium',
                position_info: Optional[Dict] = None,
                mode: str = 'daily',
                metrics: Optional[Dict] = None) -> Optional[Dict]:
        """
        分析十字星形态
        
        Args:
            df: K线数据DataFrame（日线模式）
            today_data: 当日累计数据（分时模式，暂不支持）
            stock_type: 股票类型
            position_info: 持仓信息
            mode: 'intraday' | 'daily'（暂只支持daily）
            metrics: 预计算的公共指标（共享策略数据）
        
        Returns:
            形态分析结果，如果无形态返回 None
        """
        # 暂时只支持日线模式
        if mode == 'intraday':
            return None
        
        if df is None or len(df) == 0:
            return None
        
        current = df.iloc[-1]
        shadow_data = self.calculate_shadow_ratios(current)
        
        # K线振幅过小，不分析
        if shadow_data['total_range'] < 0.5:
            return None
        
        # 十字星特征：实体很小
        body_ratio = shadow_data['body_ratio']
        max_body_ratio = self.config.get('max_body_ratio', 0.1)  # 实体不超过总长度的10%
        
        if body_ratio > max_body_ratio:
            return None
        
        # 要求上下影线都存在
        min_shadow_ratio = self.config.get('min_shadow_ratio', 0.3)
        if shadow_data['upper_ratio'] < min_shadow_ratio or \
           shadow_data['lower_ratio'] < min_shadow_ratio:
            return None
        
        # 计算位置和量能
        price_position = self.calculate_price_position(df)
        volume_ratio = self.calculate_volume_ratio(df)
        
        # 判断信号类型
        signal_type = self._determine_signal_type(price_position, df)
        
        # 计算强度
        strength = self._calculate_strength(shadow_data, price_position, volume_ratio)
        
        return {
            'pattern': 'doji',
            'signal_type': signal_type,
            'strength': strength,
            'stock_type': stock_type,
            'details': {
                'body_ratio': body_ratio,
                'upper_shadow_ratio': shadow_data['upper_ratio'],
                'lower_shadow_ratio': shadow_data['lower_ratio'],
                'price_position': price_position,
                'volume_ratio': volume_ratio,
            }
        }
    
    def _determine_signal_type(self, price_position: float, df: pd.DataFrame) -> str:
        """
        根据位置判断信号类型
        
        - 高位十字星：可能见顶
        - 低位十字星：可能见底
        - 中位十字星：观察信号
        """
        if price_position >= 0.8:
            return 'sell'  # 高位警告
        elif price_position <= 0.3:
            return 'buy'   # 低位机会
        else:
            return 'neutral'  # 中性观察
    
    def _calculate_strength(self, shadow_data: Dict, price_position: float, 
                           volume_ratio: float) -> float:
        """计算十字星信号强度"""
        score = 1.0  # 基础分
        
        # 实体越小，越标准
        if shadow_data['body_ratio'] < 0.05:
            score += 0.5
        
        # 高位或低位加分
        if price_position >= 0.8 or price_position <= 0.3:
            score += 0.5
        
        # 放量加分
        if volume_ratio >= 1.5:
            score += 0.5
        
        return min(score, 3.0)


class HammerPattern(CandlestickPattern):
    """
    锤子线形态检测器
    
    功能：
    识别锤子线（锤头）和倒锤子线，通常是底部反转信号
    
    典型特征：
    - 锤子线：长下影线，上影线短或无，实体在上方
    - 倒锤子：长上影线，下影线短或无，实体在下方
    """
    
    def __init__(self, config: Dict):
        """
        初始化锤子线检测器
        
        Args:
            config: 锤子线配置参数
        """
        self.config = config
        self.name = 'hammer'
    
    def analyze(self, 
                df: Optional[pd.DataFrame] = None,
                today_data: Optional[Dict] = None,
                stock_type: str = 'medium',
                position_info: Optional[Dict] = None,
                mode: str = 'daily',
                metrics: Optional[Dict] = None) -> Optional[Dict]:
        """
        分析锤子线形态
        
        Args:
            df: K线数据DataFrame（日线模式）
            today_data: 当日累计数据（分时模式，暂不支持）
            stock_type: 股票类型
            position_info: 持仓信息
            mode: 'intraday' | 'daily'（暂只支持daily）
            metrics: 预计算的公共指标（共享策略数据）
        
        Returns:
            形态分析结果，如果无形态返回 None
        """
        # 暂时只支持日线模式
        if mode == 'intraday':
            return None
        
        if df is None or len(df) == 0:
            return None
        
        current = df.iloc[-1]
        shadow_data = self.calculate_shadow_ratios(current)
        
        # K线振幅过小，不分析
        if shadow_data['total_range'] < 0.5:
            return None
        
        # 检查正锤子线（底部反转）
        hammer_result = self._check_hammer(df, shadow_data, stock_type)
        if hammer_result:
            return hammer_result
        
        # 检查倒锤子线
        inverted_result = self._check_inverted_hammer(df, shadow_data, stock_type)
        if inverted_result:
            return inverted_result
        
        return None
    
    def _check_hammer(self, df: pd.DataFrame, shadow_data: Dict, 
                     stock_type: str) -> Optional[Dict]:
        """
        检查正锤子线
        
        特征：
        - 长下影线（至少是实体的2倍）
        - 上影线很短或无
        - 实体较小
        - 通常出现在下跌趋势底部
        """
        lower_ratio = shadow_data['lower_ratio']
        upper_ratio = shadow_data['upper_ratio']
        body_ratio = shadow_data['body_ratio']
        
        # 下影线要长
        min_lower_ratio = self.config.get('min_lower_shadow_ratio', 0.6)
        if lower_ratio < min_lower_ratio:
            return None
        
        # 上影线要短
        max_upper_ratio = self.config.get('max_upper_shadow_ratio', 0.1)
        if upper_ratio > max_upper_ratio:
            return None
        
        # 实体要小
        max_body_ratio = self.config.get('max_body_ratio', 0.25)
        if body_ratio > max_body_ratio:
            return None
        
        # 价格位置检查（应该在低位）
        price_position = self.calculate_price_position(df)
        max_position = self.config.get('max_price_position', 0.4)
        
        if price_position > max_position:
            return None
        
        # 计算强度
        volume_ratio = self.calculate_volume_ratio(df)
        strength = self._calculate_strength(lower_ratio, price_position, volume_ratio)
        
        return {
            'pattern': 'hammer',
            'signal_type': 'buy',
            'hammer_type': 'normal',
            'strength': strength,
            'stock_type': stock_type,
            'details': {
                'lower_shadow_ratio': lower_ratio,
                'upper_shadow_ratio': upper_ratio,
                'body_ratio': body_ratio,
                'price_position': price_position,
                'volume_ratio': volume_ratio,
                'is_bullish': self.is_bullish_candle(df.iloc[-1]),
            }
        }
    
    def _check_inverted_hammer(self, df: pd.DataFrame, shadow_data: Dict, 
                              stock_type: str) -> Optional[Dict]:
        """
        检查倒锤子线
        
        特征：
        - 长上影线
        - 下影线很短或无
        - 实体较小
        - 通常出现在下跌趋势底部
        """
        upper_ratio = shadow_data['upper_ratio']
        lower_ratio = shadow_data['lower_ratio']
        body_ratio = shadow_data['body_ratio']
        
        # 上影线要长
        min_upper_ratio = self.config.get('min_upper_shadow_ratio', 0.6)
        if upper_ratio < min_upper_ratio:
            return None
        
        # 下影线要短
        max_lower_ratio = self.config.get('max_lower_shadow_ratio', 0.1)
        if lower_ratio > max_lower_ratio:
            return None
        
        # 实体要小
        max_body_ratio = self.config.get('max_body_ratio', 0.25)
        if body_ratio > max_body_ratio:
            return None
        
        # 价格位置检查（应该在低位）
        price_position = self.calculate_price_position(df)
        max_position = self.config.get('max_price_position', 0.4)
        
        if price_position > max_position:
            return None
        
        # 计算强度
        volume_ratio = self.calculate_volume_ratio(df)
        strength = self._calculate_strength(upper_ratio, price_position, volume_ratio)
        
        return {
            'pattern': 'hammer',
            'signal_type': 'buy',
            'hammer_type': 'inverted',
            'strength': strength,
            'stock_type': stock_type,
            'details': {
                'upper_shadow_ratio': upper_ratio,
                'lower_shadow_ratio': lower_ratio,
                'body_ratio': body_ratio,
                'price_position': price_position,
                'volume_ratio': volume_ratio,
                'is_bullish': self.is_bullish_candle(df.iloc[-1]),
            }
        }
    
    def _calculate_strength(self, shadow_ratio: float, price_position: float, 
                           volume_ratio: float) -> float:
        """计算锤子线信号强度"""
        score = 1.0  # 基础分
        
        # 影线越长，越标准
        if shadow_ratio >= 0.7:
            score += 1.0
        elif shadow_ratio >= 0.6:
            score += 0.5
        
        # 位置越低，越有效
        if price_position <= 0.2:
            score += 1.0
        elif price_position <= 0.3:
            score += 0.5
        
        # 放量加分
        if volume_ratio >= 1.5:
            score += 0.5
        
        return min(score, 3.0)

